Lambdaで利用できるDead Letter Queue(DLQ)とはどんな機能であるか調べてみた
こんにちは、CX事業本部の若槻です。
本エントリは、AWS LambdaとServerless #2 Advent Calendar 2019の23日目です。
ジョインした案件のLambdaの実装でDead Letter Queue(DLQ)が使われており、このDLQがどのような機能であるかよく理解できていなかったため、動作検証含め調べてみました。
Dead Letter Queueとは
When you invoke a function asynchronously, Lambda sends the event to a queue. A separate process reads events from the queue and runs your function. When the event is added to the queue, Lambda returns a success response without additional information. To invoke a function asynchronously, set the invocation type parameter to Event.
(中略)
The output file (response.json) doesn't contain any information, but is still created when you run this command. If Lambda isn't able to add the event to the queue, the error message appears in the command output.
Lambda manages the function's asynchronous invocation queue and attempts to retry failed events automatically. If the function returns an error, Lambda attempts to run it two more times, with a one-minute wait between the first two attempts, and two minutes between the second and third attempts. Function errors include errors returned by the function's code and errors returned by the function's runtime, such as timeouts.
When all attempts to process an asynchronous invocation fail, Lambda can send the event to an Amazon SQS queue or an Amazon SNS topic. Configure your function with a dead-letter queue to save these events for further processing.
(中略)
To send events to a queue or topic, your function needs additional permissions. Add a policy with the required permissions to your function's execution role. ・Amazon SQS – sqs:SendMessage ・Amazon SNS – sns:Publish
Asynchronous invocation – Lambda retries function errors twice. If the function doesn't have enough capacity to handle all incoming requests, events might wait in the queue for hours or days to be sent to the function. You can configure a dead-letter queue on the function to capture events that weren't successfully processed.
上記のAWSドキュメントの記述を要約すると、
- 関数を非同期で呼び出すと、Lambdaはイベントをキューに送信する。別のプロセスがキューからイベントを読み取り、関数を実行する。
- イベントがキューに追加されるとLambdaは呼び出し元に成功応答を返す。
- Lambdaはキューを管理し、関数の実行が失敗した場合は、1回目の試行の後に1分、2回目の試行の後に2分待機して自動的に関数の実行を再試行する。
- Lambda関数にDLQを設定することにより、非同期呼び出しが失敗したイベントをキャプチャして、SQS queueまたはSNS topicに送信することができる。
- DLQを設定するLambda関数の実行ロールには、宛先によって
sqs:SendMessage
またはsns:Publish
の権限が必要である。
とのこと。つまり、LambdaにDLQを設定することにより、呼び出し元に成功応答しか返さない非同期呼び出しの場合でも、関数実行の失敗をSQS queueやSNS topicに送信してエラー監視をできるようになる!ようです。
動作検証してみた
ここで作成するLambda関数のランタイムは一律でPython 3.7
とします。
DLQを設定しない場合
成功するLambdaの場合
以下の実行が成功するLambda関数をsuccess-func
という名前で作成します。
import json def lambda_handler(event, context): # TODO implement return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
成功するLambda関数をAWS CLIで呼び出します。
- 同期呼び出し(
--invocation-type RequestResponse
)
$ aws lambda invoke \ --function-name success-func \ --invocation-type RequestResponse \ response.json { "StatusCode": 200, "ExecutedVersion": "$LATEST" }
- 非同期呼び出し(
--invocation-type Event
)
$ aws lambda invoke \ --function-name success-func \ --invocation-type Event \ response.json { "StatusCode": 202
失敗するLambdaの場合
以下の実行が失敗するLambda関数をfail-func
という名前で作成します。
import json def lambda_handler(event, context): # raise error raise ValueError("エラー!") # TODO implement return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
失敗するLambda関数をAWS CLIで呼び出します。
- 同期呼び出し(
--invocation-type RequestResponse
)
$ aws lambda invoke \ --function-name fail-func \ --invocation-type RequestResponse \ response.json { "StatusCode": 200, "FunctionError": "Unhandled", "ExecutedVersion": "$LATEST" }
ログストリームに関数を1回だけ実行した際のエラーが出力されています。
- 非同期呼び出し(
--invocation-type Event
)
$ aws lambda invoke \ --function-name fail-func \ --invocation-type Event \ response.json { "StatusCode": 202 }
ログストリームに関数を初回、1分後の2回目、2分後の3回目を実行した際のエラーが出力されています。
非同期呼び出しの場合は、関数の実行が成功/失敗のいずれの場合も、呼び出されるLambda側のログにはエラーが記録されますが、呼び出し側からすると"StatusCode": 202
しか返らずエラー発生が検知できない結果となりました。
DLQを設定する場合
実際にLambda関数fail-func
にDLQを設定して、非同期呼び出しした関数の実行失敗時の動作を見てみます。
- SNSトピックの作成
Lambda関数fail-func
の実行エラー時のDLQの送信先となるSNSトピックfailed-lambda
を作成します。
$ aws sns create-topic --name failed-lambda { "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:failed-lambda" }
作成したトピックをsubscribeする設定をします。SMSの場合は認証は不要です。
$ aws sns subscribe --topic-arn arn:aws:sns:ap-northeast-1:123456789012:failed-lambda \ --protocol SMS \ --notification-endpoint +818012345678 { "SubscriptionArn": "arn:aws:sns:ap-northeast-1:123456789012:failed-lambda:2f45bdc4-5e14-49a4-abde-2ed84752b2bc" }
- Lambda実行ロールに権限追加
Lambda関数fail-func
がSNSトピックfailed-lambda
にPublish可能とするIAMポリシーを作成します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "sns:Publish" ], "Resource": "arn:aws:sns:ap-northeast-1:123456789012:failed-lambda" } ] }
$ aws iam create-policy --policy-name AllowDeadLetterQueueForLambdaPolicy \ --policy-document file://AllowDeadLetterQueueForLambdaPolicy.json { "Policy": { "PolicyName": "AllowDeadLetterQueueForLambdaPolicy", "PolicyId": "AAAAAAAAAAAAAAAAAAAAA", "Arn": "arn:aws:iam::123456789012:policy/AllowDeadLetterQueueForLambdaPolicy", "Path": "/", "DefaultVersionId": "v1", "AttachmentCount": 0, "PermissionsBoundaryUsageCount": 0, "IsAttachable": true, "CreateDate": "2019-12-22T15:50:23Z", "UpdateDate": "2019-12-22T15:50:23Z" } }
作成したポリシーを、失敗するLambda関数fail-func
の実行ロール(例:lambda_basic_execution
)に追加で適用します。
$ aws iam attach-role-policy \ --role-name lambda_basic_execution \ --policy-arn arn:aws:iam::123456789012:policy/AllowDeadLetterQueueForLambdaPolicy
- Lambda関数のDeadLetterConfigの設定
Lambda関数fail-func
の設定を更新して、先ほど作成したSNSトピックfailed-lambda
をDLQの送信先として指定します。
$ aws lambda update-function-configuration \ --function-name fail-func \ --dead-letter-config TargetArn=arn:aws:sns:ap-northeast-1:123456789012:failed-lambda { [...] "FunctionName": "fail-func", "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:fail-func", "Runtime": "python3.7", "Role": "arn:aws:iam::123456789012:role/Lambda_Basic_Role", "Handler": "lambda_function.lambda_handler", "DeadLetterConfig": { "TargetArn": "arn:aws:sns:ap-northeast-1:123456789012:failed-lambda" }, [...] }
- 非同期呼び出し(
--invocation-type Event
)
失敗するLambda関数をAWS CLIで呼び出します。
$ aws lambda invoke \ --function-name fail-func \ --invocation-type Event \ response.json { "StatusCode": 202 }
ログストリームに関数が3回実行された際のエラーが出力されています。
subscribeに登録した電話番号には[NOTICE]という送信元から下記のようなSMSが届きました。
デフォルトではSMSはエラー通知内容の情報量がまったくありませんでした。ちなみにEメールをsubscriptionに設定した場合は下記のような内容のエラー通知が届きます。少なくともどの関数でエラーが発生したのかまでは分かります。
{"version":"0","id":"54a6ad92-6f5d-8ab2-2834-30fe98ad41c8","detail-type":"Scheduled Event","source":"aws.events","account":"123456789012","time":"2019-12-22T05:08:02Z","region":"ap-northeast-1","resources":["arn:aws:events:ap-northeast-1:123456789012:rule/fail-func"],"detail":{}}
これでLambda関数の非同期呼び出し時のDLQによる通知の動作確認を行うことができました。
Lambda関数を非同期的に呼び出すサービス
他のサービスで AWS Lambda を使用するによると、以下のようなサービスがLambdaの非同期呼び出しを行うとのことです。これらのサービスからLambdaを呼び出す際にDLQの利用を検討することになるかと思います。
- Amazon Simple Storage Service
- Amazon Simple Notification Service
- Amazon Simple Email Service
- AWS CloudFormation
- Amazon CloudWatch Logs
- Amazon CloudWatch Events
- AWS CodeCommit
- AWS Config
- AWS IoT Events
- AWS CodePipeline
おわりに
本記事ではDLQの仕様確認と、Lambda関数の非同期呼び出し時のDLQによる通知の動作確認を行いました。DLQの必要性と設定方法を理解することができました。今回はSNSトピックのsubscriptionにSMSやEメールを設定したため通知内容は定形のもののみでしたが、実際にはLambda関数をsubscriptionに設定して通知内容をカスタマイズすることになるかと思います。参考になれば幸いです。